指针的使用

指针的用法

指针在C语言中的地位就好比反射之于java/C#,起着十分重要的作用。指针能让萌新落泪,让大神起飞,让C语言屹立不倒。正是指针的存在让C语言变得十分灵活但同样也带来了一些令程序员头疼的问题。

为什么需要指针

指针,顾名思义是指向一个地方的标识,与存储有着密切的关系。指针指向内存中的一个地址,这个地址可能包含存储的信息,或者指向信息的指针(指针也一种信息),这样才能很好的管理内存。如果不给内存划分区域,编上地址,我们根本无法描述我们要比信息存储在哪,就行我们定位一个地方也是通过地址来找。以此指针就是告诉程序变量存放的位置,这样程序就能找到地方取出或存入信息。

如何定义指针

定义指针只需要在 基本类型后加*即可,以下是示例:

1
2
3
4
5
int* i_num\\指向一个int变量的指针
char* ch\\指向字符类型的指针
float* f_num
int** num\\指向指针的指针,即num指向的地址存放的也是一个地址
struct student* st_point\\指向结果体的指针

指针的基操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//一些简单的准备
int a=3;
int b[]={23,45,6787,98};
int c[4][5];//二维数组,可理解为矩阵

struct task{
int count;
char Descirption[50];
};

struct student{
char name[20];
int id;
void Learning(){
printf("Learning something");
}
struct task tasks;
};

struct student st;


//声明指针并赋值
int* p=&a;// &是取地址符,获取变量的地址

//数组的特性
int* p1=b;/*一维数组名是数组存储的首地址,相当于一个指针,不同的是一维数组名是一个指针常量,即指向固定的地址,不能给它赋值来改变其指向的地址(注意是不能赋值,而不是赋值了不改变地址)*/
int** p2=c;//类似的,二维数组名是一个二重指针

struct student* stp=&st;/*不同于数组,结构体名虽然也是结构体首地址,但不能当成指针传递(实际上数组名也不是指针,只是赋值的时候自动转换了)*/


//访问指针指向的地址存储的信息,用*
*a//取出a存储的值
*b//取出b的地址存储的值,即b[0]的值23
*c//c[0][0]的值

//结构体的不同,用->('-'加上'>'中间不能有空格)访问,如
(*stp).name[0]='a';
stp->id=4;
stp->tasks.count=6;
//step.task->count//这是错误的,->只适用于结构体指针类型,step.task以不是指针

//指针的运算
p++;//指针+1,表示指针的地址+一个数据类型长度,对于int型来说是4个字节
p--;
p+3;
--p;

二维数组指针详解

在写代码之前我们来看看二维数组在内存中存储的方式

对于一维数组,存储肯定是线性的,但是二维数组并不是以矩阵的形式存储,还是以线性的方式存储,如下图:

mark

对于二维数组可以理解为以一元数组为元素的数组,所以a相当于一个二维指针(但不是),a[0]代表一个一维数组,a[0]是这个数组的名字,即指向a[0][0]到a[0][3]这块内存的首地址。

1
2
3
4
5
6
7
8
9
10
11
int a[4][4];
int*p1;
int**p2;
p1=a[0];//正确
//p2=a;//错误,a实际上不是一个指针

p2=(int**)a;//因为a相当于一个指针,因此可以强制转换
int(*p2)[4]=a;//这样也可以,P2是二维指针,理解为p2是int[4]的指针

p1++;/*指针移动4个字节(1个int的存储大小),因为p1指向的数组存储int,如果移动的距离不是4个字节会导致数据存储重叠或者有空隙,读取的时候会有问题*/
p2++;//p2指向的数组以int[4]为元素,因此移动16个字节

指针之痛——野指针

在使用指针处理有关数组的问题时往往会简化程序,有相当大的灵活性。但是在使用中往往出现野指针的问题,所谓野指针就是指向不知道的地方的指针。

产生野指针的常见错误有两种:

  1. 声明指针后没有初始化,这是初学者经常犯的毛病,不多说。
  2. 使用指针的自增运算时没有节制,超出数组存储的范围;操作指针后没有复位,再次操作时出现越界,典型的有使用指针遍历数组后指针指向最后一个元素,但是再次使用指针访问数组前没有复位。
  3. 在方法中返回一个局部的指针变量,因为局部变量是有生命周期的,当函数运行完毕就会销毁。函数最后一个语句一般是return,当你return一个指针的时候,这个指针还是存在的,即指针指向地址存储着你需要的信息,但是一旦return执行完毕,这个局部指针就会被销毁(其中的值被销毁,内存的地址编码是固定的,地址是不会被消除的)。当你在外面的程序访问时,你访问的确实还是这个地址,但是里面的内容是不可预测的,因为局部指针销毁后,程序会认为这个地址对应的存储空间是空闲的,会分配给程序中的其他变量。所以从效果上看就像是指针不知道指到哪里去了,也就是野指针。

指针传参与形参传参的区别

1
2
3
4
5
6
void change_value_1(int a){
a=a*a+1;
}
void change_value_1(int* a){
*a=(*a)*(*a)+1;
}

对于这两个函数,调用执行的效果是不一样的,对于第一个方法,使用的是形参,形参可以看做是函数的局部变量,传递参数的过程可以等效为在函数中声明一个局部变量,再把传递的实参的值赋给这个局部变量。因此我们后续在函数中操作这个变量都不会影响到传递过来的实参,相当于我们开了个副本来操作,当函数执行完毕就会销毁。但如果参数是个指针,同样的我们也是声明一个局部指针,拷贝传参的实参,但是不一样的是:当地址一样时,我们访问的就是同一个变量,所以我们在函数通过局部指针访问和在程序外通过实参访问的是一个变量,修改自然也就会生效。

关于指针有太多的用法和注意点,学好指针是每个合格的C程序员所必须的,本文也只是讲了最基础的语法,指针的运用才是最为迷人的。

文章目录
  1. 1. 指针的用法
    1. 1.0.1. 为什么需要指针
    2. 1.0.2. 如何定义指针
    3. 1.0.3. 指针的基操
    4. 1.0.4. 二维数组指针详解
    5. 1.0.5. 指针之痛——野指针
    6. 1.0.6. 指针传参与形参传参的区别